﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data;
using StyleMVVM.Data.Collections;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.Validation;
using Windows.Foundation.Collections;

namespace StyleMVVM.ViewModel
{
	public class DataModelViewModel<TViewModel, T> : BaseViewModel, IObservableMap<string, object> where T : class
	{
		private static string supplemental = typeof(TViewModel).FullName;
		private static List<string> properties;
		private T dataModel;
		protected IValidationContext dataModelValidationContext;

		static DataModelViewModel()
		{
			properties = new List<string>();

			foreach (PropertyInfo declaredProperty in typeof(T).GetTypeInfo().DeclaredProperties)
			{
				properties.Add(declaredProperty.Name);
			}
		}

		[ImportMethod]
		public void ImportDependencies(IValidationService validationService,
		                               IReflectionService reflectionService,
		                               IValidationContext validationContext)
		{
			ValidationService = validationService;
			ReflectionService = reflectionService;
			ValidationContext = validationContext;
		}

		[ActivationComplete]
		public virtual void ActivationComplete()
		{
			if (dataModel != null &&
			    dataModelValidationContext == null)
			{
				dataModelValidationContext = ValidationService.GetValidationContext(dataModel);

				dataModelValidationContext.LinkContex(ValidationContext);
			}
		}

		protected IReflectionService ReflectionService { get; private set; }

		protected IValidationContext ValidationContext { get; private set; }

		protected IValidationService ValidationService { get; private set; }

		protected T DataModel
		{
			get { return dataModel; }
			set
			{
				if (dataModel != value)
				{
					INotifyPropertyChanged oldPropertyChanged = dataModel as INotifyPropertyChanged;

					if (oldPropertyChanged != null)
					{
						oldPropertyChanged.PropertyChanged -= DataModelOnPropertyChanged;
					}

					if (dataModelValidationContext != null)
					{
						dataModelValidationContext.UnlinkContex(ValidationContext);

						dataModelValidationContext = null;
					}

					dataModel = value;

					INotifyPropertyChanged newPropertyChanged = dataModel as INotifyPropertyChanged;

					if (newPropertyChanged != null)
					{
						newPropertyChanged.PropertyChanged += DataModelOnPropertyChanged;
					}

					foreach (string propertyName in properties)
					{
						try
						{
							OnMapChanged(propertyName);
						}
						catch (Exception exp)
						{
							Logger.Error(
								string.Format("Exception thrown while raising property {0} on type {1} ", propertyName, GetType().FullName),
								supplemental,
								exp);
						}
					}

					if (dataModel != null && ValidationService != null)
					{
						dataModelValidationContext = ValidationService.GetValidationContext(dataModel, true, true);

						dataModelValidationContext.LinkContex(ValidationContext);
					}
				}
			}
		}

		private void DataModelOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
		{
			OnMapChanged(propertyChangedEventArgs.PropertyName);
		}

		public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
		{
			List<KeyValuePair<string, object>> returnValue = new List<KeyValuePair<string, object>>();

			if (DataModel != null && ReflectionService != null)
			{
				foreach (string property in properties)
				{
					object value = ReflectionService.GetPropertyValue(DataModel, property);

					returnValue.Add(new KeyValuePair<string, object>(property, value));
				}
			}

			return returnValue.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		public void Add(KeyValuePair<string, object> item)
		{
			if (DataModel != null)
			{
				ReflectionService.SetPropertyValue(DataModel, item.Key, item.Value);
			}
		}

		public void Clear()
		{
			throw new NotImplementedException();
		}

		public bool Contains(KeyValuePair<string, object> item)
		{
			return properties.Contains(item.Key);
		}

		public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
		{
			throw new NotImplementedException();
		}

		public bool Remove(KeyValuePair<string, object> item)
		{
			if (DataModel != null)
			{
				ReflectionService.SetPropertyValue(DataModel, item.Key, null);

				return true;
			}

			return false;
		}

		public int Count
		{
			get { return properties.Count; }
		}

		public virtual bool IsReadOnly
		{
			get { return false; }
		}

		public void Add(string key, object value)
		{
			if (DataModel != null)
			{
				ReflectionService.SetPropertyValue(DataModel, key, value);
			}
		}

		public bool ContainsKey(string key)
		{
			return properties.Contains(key);
		}

		public bool Remove(string key)
		{
			if (DataModel != null)
			{
				ReflectionService.SetPropertyValue(DataModel, key, null);

				return true;
			}

			return false;
		}

		public bool TryGetValue(string key, out object value)
		{
			bool returnValue = false;

			if (DataModel != null)
			{
				value = ReflectionService.GetPropertyValue(DataModel, key);

				if (value != null || properties.Contains(key))
				{
					returnValue = true;
				}
			}
			else
			{
				value = null;
			}

			return returnValue;
		}

		public object this[string key]
		{
			get
			{
				if (DataModel != null)
				{
					return ReflectionService.GetPropertyValue(DataModel, key);
				}

				return null;
			}
			set
			{
				if (DataModel != null)
				{
					ReflectionService.SetPropertyValue(DataModel, key, value);
				}
			}
		}

		public ICollection<string> Keys
		{
			get { return properties; }
		}

		public ICollection<object> Values
		{
			get
			{
				List<object> returnValues = new List<object>();

				if (dataModel != null)
				{
					foreach (string property in properties)
					{
						object value = ReflectionService.GetPropertyValue(DataModel, property);

						if (value != null)
						{
							returnValues.Add(value);
						}
					}
				}

				return returnValues;
			}
		}

		public event MapChangedEventHandler<string, object> MapChanged;

		protected void OnMapChanged(string propertyName)
		{
			if (MapChanged != null)
			{
				MapChanged(this, new ObservableDictionaryChangedEventArgs<string>(CollectionChange.ItemChanged, propertyName));
			}
		}
	}
}